0%

Self-Learned Algorithms - 06

Self-Learned Algorithms - 06

This is my learning note about algorithms.

Reference


239.滑动窗口最大值

https://leetcode-cn.com/problems/sliding-window-maximum/

题解

  • 一共有L-k+1个窗口

解法

  • 暴力法:遍历每一个窗口然后找到每一个窗口的最大值
1
2
3
4
n = len(nums)
if n * k == 0:
return []
return [max(nums[i:i + k]) for i in range(n - k + 1)]
  • 线性解法:双端队列(用堆虽然可以方便的获得最大值,但插入值的时间复杂度为$O(N\log k)$)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# base cases
n = len(nums)
if n * k == 0:
return []
if k == 1:
return nums

def clean_deque(i):
# remove indexes of elements not from sliding window
if deq and deq[0] == i - k:
deq.popleft()
# remove from deq indexes of all elements
# which are smaller than current element nums[i]
while deq and nums[i] > nums[deq[-1]]:
deq.pop()

# init deque and output
deq = deque()
max_idx = 0
for i in range(k):
clean_deque(i)
deq.append(i)
# compute max in nums[:k]
if nums[i] > nums[max_idx]:
max_idx = i
output = [nums[max_idx]]

# build output
for i in range(k, n):
clean_deque(i)
deq.append(i)
output.append(nums[deq[0]])
return output

比官方解法看起来更简洁一点的

1
2
3
4
5
6
7
8
9
deque = collections.deque()
res = []
for i, num in enumerate(nums):
while deque and deque[0] <= i - k: deque.popleft() # outdate indices # 'while' can be changed into 'if'
while deque and num > nums[deque[-1]]: deque.pop()
deque.append(i)
if i >= k - 1:
res.append(nums[deque[0]])
return res
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
n = len(nums)
if n * k == 0:
return []
if k == 1:
return nums

left = [0] * n
left[0] = nums[0]
right = [0] * n
right[n - 1] = nums[n - 1]
for i in range(1, n):
# from left to right
if i % k == 0:
# block start
left[i] = nums[i]
else:
left[i] = max(left[i - 1], nums[i])
# from right to left
j = n - i - 1
if (j + 1) % k == 0:
# block end
right[j] = nums[j]
else:
right[j] = max(right[j + 1], nums[j])

output = []
for i in range(n - k + 1):
output.append(max(left[i + k - 1], right[i]))

return output

3.无重复字符的最长子串

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

题解

  • 可以使用滑动窗口解题的前提:假设我们选择字符串中的第$k$个字符作为起始位置,并且得到了不包含重复字符的最长子串的结束位置为$r_k$。那么当我们选择第$k+1$个字符作为起始位置时,首先从$k+1$到$r_k$的字符显然是不重复的,并且由于少了原本的第$k$个字符,我们可以尝试继续增大$r_k$,直到右侧出现了重复字符为止。

解法

  • 滑动窗口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 哈希集合,记录每个字符是否出现过
occ = set()
n = len(s)
# 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
rk, ans = -1, 0
for i in range(n):
if i != 0:
# 左指针向右移动一格,移除一个字符
occ.remove(s[i - 1])
while rk + 1 < n and s[rk + 1] not in occ:
# 不断地移动右指针
occ.add(s[rk + 1])
rk += 1
# 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = max(ans, rk - i + 1)
return ans
1
2
3
4
5
6
7
8
9
k, res, c_dict = -1, 0, {}
for i, c in enumerate(s):
if c in c_dict and c_dict[c] > k: # 字符c在字典中 且 上次出现的下标大于当前长度的起始下标
k = c_dict[c]
c_dict[c] = i
else:
c_dict[c] = i
res = max(res, i-k)
return res

438.找到字符串中所有字母异位词

https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/

题解

  • 字母异位词指字母相同,但排列不同的字符串。

解法

  • 滑动窗口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
res = []
window = {} # 记录窗口中各个字符数量的字典
needs = {} # 记录目标字符串中各个字符数量的字典
for c in p: needs[c] = needs.get(c, 0) + 1 # 统计目标字符串的信息

length, limit = len(p), len(s)
left = right = 0 # 定理两个指针,分别表示窗口的左、右界限

while right < limit:
c = s[right]
if c not in needs: # 当遇到不需要的字符时
window.clear() # 将之前统计的信息全部放弃
left = right = right + 1 # 从下一位置开始重新统计
else:
window[c] = window.get(c, 0) + 1 # 统计窗口内各种字符出现的次数
if right-left+1 == length: # 当窗口大小与目标字符串长度一致时
if window == needs: res.append(left) # 如果窗口内的各字符数量与目标字符串一致就将left添加到结果中
window[s[left]] -= 1 # 并将移除的字符数量减一
left += 1 # left右移
right += 1 # right右移
return res

另一种写法:用数组比较是否相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 记录p, s字母个数
p_count = [0] * 26
s_count = [0] * 26
res = []
for a in p:
p_count[ord(a) - 97] += 1
left = 0
for right in range(len(s)):
# print(p_count, s_count)
if right < len(p) - 1:
s_count[ord(s[right]) - 97] += 1
continue
# 窗口加一个, 减一个,维护长度为len(p)的长度
s_count[ord(s[right]) - 97] += 1
if p_count == s_count:
res.append(left)
s_count[ord(s[left]) - 97] -= 1
left += 1
return res
  • 哈希表+前缀:按前缀统计字母的个数,这样便可方便求出和p等长的字符串是否一致。
1
2
3
4
5
6
7
8
9
10
11
12
from collections import Counter
n = len(p)
p = Counter(p)
dp = [0] * (len(s) + 1)
dp[0] = Counter()
res = []
for i in range(1, len(s) + 1):
dp[i] = dp[i - 1].copy()
dp[i][s[i - 1]] += 1
if i >= n and dp[i] - dp[i - n] == p:
res.append(i - n)
return res

[滑动窗口解决字符串匹配问题][https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/solution/hua-dong-chuang-kou-tong-yong-si-xiang-jie-jue-zi-/]